using System;
using System.Collections.Generic;
using System.Text;

namespace GoogleCalendarReminder
{
    /// <summary>
    /// A class which provides a service to retrieve events from a Google Calendar and can monitor these events for
    /// any that have a popup reminder that occurs at the current moment in time when the service is running.
    /// </summary>
    public class EventService
    {
        #region Private Data Members

        private EventEntryWrapperCollection _events;
        private System.Timers.Timer _hourTimer;
        private System.Timers.Timer _minuteTimer;
        private System.Timers.Timer _pauseTimer;
        private int _pauseMinuteCount;
        private Google.GData.Calendar.CalendarService _service;
        private object lockObject = new object();
        private EventServiceState _state = EventServiceState.Stopped;
        private System.ComponentModel.AsyncOperation _asyncOp;
        private System.Threading.SendOrPostCallback _onExceptionOccured;
        private System.Threading.SendOrPostCallback _onEventOccured;
        private System.Threading.SendOrPostCallback _onPausedOccured;
        private System.Threading.SendOrPostCallback _onResumedOccured;
        private System.Threading.SendOrPostCallback _onStartedOccured;
        private System.Threading.SendOrPostCallback _onStoppedOccured;
        private Exception _exception;

        private static System.Collections.Specialized.HybridDictionary _asyncIds =
            new System.Collections.Specialized.HybridDictionary();

        #endregion

        #region Private Functions

        /// <summary>
        /// Refreshes the events.
        /// </summary>
        /// <exception cref="Google.GData.Client.LoggedException">An error occured while communicating with the Google Calendar service.</exception>
        private void _hourTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            try
            {
                lock (lockObject)
                {
                    refreshEvents();
                }
            }
            catch (Google.GData.Client.LoggedException ex)
            {
                Stop();

                _asyncOp.Post(
                    _onExceptionOccured,
                    new System.ComponentModel.AsyncCompletedEventArgs(ex, true, _asyncOp.UserSuppliedState));

                _exception = ex;
            }
        }

        /// <summary>
        /// Scans the events.
        /// </summary>
        private void _minuteTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            lock (lockObject)
            {
                scanEvents();
            }
        }

        /// <summary>
        /// Increments the pause minute count.
        /// </summary>
        private void _pauseTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            _pauseMinuteCount++;
        }

        /// <summary>
        /// Handles cross thread event handling.
        /// </summary>
        private void onEventOccuredHandler(object e)
        {
            EventEntryWrapperEventArgs ex = e as EventEntryWrapperEventArgs;
            OnEventOccured(ex);
        }

        /// <summary>
        /// Handles cross thread event handling.
        /// </summary>
        private void onExceptionOccuredHandler(object e)
        {
            System.ComponentModel.AsyncCompletedEventArgs ex =
                e as System.ComponentModel.AsyncCompletedEventArgs;
            OnExceptionOccured(ex);
        }

        /// <summary>
        /// Handles cross thread event handling.
        /// </summary>
        private void onPausedHandler(object e)
        {
            EventArgs ex = e as EventArgs;
            OnPaused(ex);
        }

        /// <summary>
        /// Handles cross thread event handling.
        /// </summary>
        private void onResumedHandler(object e)
        {
            EventArgs ex = e as EventArgs;
            OnResumed(ex);
        }

        /// <summary>
        /// Handles cross thread event handling.
        /// </summary>
        private void onStartedHandler(object e)
        {
            EventArgs ex = e as EventArgs;
            OnStarted(ex);
        }

        /// <summary>
        /// Handles cross thread event handling.
        /// </summary>
        private void onStoppedHandler(object e)
        {
            EventArgs ex = e as EventArgs;
            OnStopped(ex);
        }

        /// <summary>
        /// Clears and repopulates the event collection.
        /// </summary>
        /// <exception cref="Google.GData.Client.LoggedException">An error occured while communicating with the Google Calendar service.</exception>
        private void refreshEvents()
        {
            DateTimeFormatter formatter = new DateTimeFormatter(DateTime.Now);

            Google.GData.Calendar.EventEntry[] events =
                EventProvider.GetEvents(
                Service,
                formatter.StartTime,
                formatter.EndTime);

            lock (_events)
            {
                _events.Clear();

                foreach (Google.GData.Calendar.EventEntry sEvent in events)
                {
                    _events.Add(new EventEntryWrapper(sEvent));
                }
            }
        }

        /// <summary>
        /// Scans the event collection and raises EventOccured if an event occurs at the current time.
        /// </summary>
        private void scanEvents()
        {
            DateTime now = DateTime.Now;

            lock (_events)
            {
                foreach (EventEntryWrapper sEvent in _events)
                {
                    foreach (WhenWrapper when in sEvent.Times)
                    {
                        if (WhenCalculator.IsReminderNow(when, now))
                        {
                            OnEventOccured(new EventEntryWrapperEventArgs(sEvent));
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Starts the timers.
        /// </summary>
        private void startTimers()
        {
            _minuteTimer.Start();
            _hourTimer.Start();
        }

        /// <summary>
        /// Stops the timers.
        /// </summary>
        private void stopTimers()
        {
            _minuteTimer.Stop();
            _hourTimer.Stop();
        }

        #endregion

        #region Protected Functions

        /// <summary>
        /// Handles the ExceptionOccured event.
        /// </summary>
        protected virtual void OnExceptionOccured(System.ComponentModel.AsyncCompletedEventArgs e)
        {
            if (ExceptionOccured != null)
            {
                ExceptionOccured(this, e);
            }
        }

        /// <summary>
        /// Handles the EventOccured event.
        /// </summary>
        protected virtual void OnEventOccured(EventEntryWrapperEventArgs e)
        {
            if (EventOccured != null)
            {
                EventOccured(this, e);
            }
        }

        /// <summary>
        /// Handles the Paused event.
        /// </summary>
        protected virtual void OnPaused(EventArgs e)
        {
            if (Paused != null)
            {
                Paused(this, e);
            }
        }

        /// <summary>
        /// Handles the Resumed event.
        /// </summary>
        protected virtual void OnResumed(EventArgs e)
        {
            if (Resumed != null)
            {
                Resumed(this, e);
            }
        }

        /// <summary>
        /// Handles the Started event.
        /// </summary>
        protected virtual void OnStarted(EventArgs e)
        {
            if (Started != null)
            {
                Started(this, e);
            }
        }

        /// <summary>
        /// Handles the Stopped event.
        /// </summary>
        protected virtual void OnStopped(EventArgs e)
        {
            if (Stopped != null)
            {
                Stopped(this, e);
            }
        }

        #endregion

        /// <summary>
        /// Creates a new instance of the GoogleCalendarReminder.EventManager class.
        /// </summary>
        public EventService()
        {
            _events = new EventEntryWrapperCollection();

            _minuteTimer = new System.Timers.Timer(60 * 1000);
            _minuteTimer.AutoReset = true;
            _minuteTimer.Elapsed += new System.Timers.ElapsedEventHandler(_minuteTimer_Elapsed);

            _hourTimer = new System.Timers.Timer(60 * 60 * 1000);
            _hourTimer.AutoReset = true;
            _hourTimer.Elapsed += new System.Timers.ElapsedEventHandler(_hourTimer_Elapsed);

            _pauseTimer = new System.Timers.Timer();
            _pauseTimer.AutoReset = true;
            _pauseTimer.Elapsed += new System.Timers.ElapsedEventHandler(_pauseTimer_Elapsed);

            _onEventOccured = new System.Threading.SendOrPostCallback(onEventOccuredHandler);
            _onExceptionOccured = new System.Threading.SendOrPostCallback(onExceptionOccuredHandler);
            _onPausedOccured = new System.Threading.SendOrPostCallback(onPausedHandler);
            _onResumedOccured = new System.Threading.SendOrPostCallback(onResumedHandler);
            _onStartedOccured = new System.Threading.SendOrPostCallback(onStartedHandler);
            _onStoppedOccured = new System.Threading.SendOrPostCallback(onStoppedHandler);

            while (true)
            {
                try
                {
                    string id = DateTime.Now.ToString();

                    lock (_asyncIds)
                    {
                        if (_asyncIds[id] != null) continue;
                        _asyncOp = System.ComponentModel.AsyncOperationManager.CreateOperation(id);
                        _asyncIds.Add(id, _asyncOp);
                        break;
                    }
                }
                catch (Exception) { }
            }
        }

        /// <summary>
        /// Creates a new instance of the GoogleCalendarReminder.EventManager class.
        /// </summary>
        /// <param name="service">The service to associate with this object.</param>
        public EventService(Google.GData.Calendar.CalendarService service)
            : this()
        {
            Service = service;
        }

        /// <summary>
        /// Removes the asyncoperation from the dictionary.
        /// </summary>
        ~EventService()
        {
            lock (_asyncIds)
            {
                _asyncIds.Remove(_asyncOp.UserSuppliedState);
            }
        }

        /// <summary>
        /// Starts the event manager service.
        /// </summary>
        /// <exception cref="System.NullReferenceException">The Service property must be set before calling the Start method.</exception>
        public void Start()
        {
            if (State == EventServiceState.Stopped)
            {
                stopTimers();

                if (Service == null) throw new NullReferenceException("The Service property must be set before calling the Start method.");

                _hourTimer_Elapsed(this, null);

                if (_exception == null)
                {
                    _minuteTimer_Elapsed(this, null);
                    startTimers();
                    _state = EventServiceState.Started;

                    _asyncOp.Post(_onStartedOccured, EventArgs.Empty);
                }

                _exception = null;
            }
        }

        /// <summary>
        /// Pauses the event manager service.
        /// </summary>
        public void Pause()
        {
            if (State == EventServiceState.Started)
            {
                stopTimers();
                _pauseMinuteCount = 0;
                _pauseTimer.Start();
                _state = EventServiceState.Paused;

                _asyncOp.Post(_onPausedOccured, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Resumes the event manager service from the paused state.
        /// </summary>
        public void Resume()
        {
            if (State == EventServiceState.Paused)
            {
                _pauseTimer.Stop();

                if (_pauseMinuteCount > 0)
                {
                    _minuteTimer_Elapsed(this, null);
                }

                startTimers();
                _state = EventServiceState.Started;

                _asyncOp.Post(_onResumedOccured, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Stops the event manager service.
        /// </summary>
        public void Stop()
        {
            if (State != EventServiceState.Stopped)
            {
                stopTimers();
                _events.Clear();
                _state = EventServiceState.Stopped;

                _asyncOp.Post(_onStoppedOccured, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Gets or sets the service.
        /// </summary>
        public Google.GData.Calendar.CalendarService Service
        {
            get { return _service; }
            set { _service = value; }
        }

        /// <summary>
        /// Gets the current state of the service.
        /// </summary>
        public EventServiceState State
        {
            get { return _state; }
        }

        /// <summary>
        /// Occurs when an exception occurs during the service lifetime.
        /// </summary>
        public event EventHandler<System.ComponentModel.AsyncCompletedEventArgs> ExceptionOccured;

        /// <summary>
        /// Occurs when an event occurs at the current moment in time.
        /// </summary>
        public event EventHandler<EventEntryWrapperEventArgs> EventOccured;

        /// <summary>
        /// Occurs when the service is paused.
        /// </summary>
        public event EventHandler Paused;

        /// <summary>
        /// Occurs when the service is resumed.
        /// </summary>
        public event EventHandler Resumed;

        /// <summary>
        /// Occurs when the service is started.
        /// </summary>
        public event EventHandler Started;

        /// <summary>
        /// Occurs when the service is stopped.
        /// </summary>
        public event EventHandler Stopped;
    }
}
